module et {
    export enum EntityStatus {
        None = 0,
        IsFromPool = 1,
        IsRegister = 1 << 1,
        IsComponent = 1 << 2,
        IsCreate = 1 << 3,
    }
    export class Entity extends AObject {

        private static readonly hashSetPool: APool<Set<Entity>> = new APool<Set<Entity>>();
        private static readonly dictPool: APool<Map<any, Entity>> = new APool<Map<any, Entity>>();
        private static readonly childrenPool: APool<Map<string, Entity>> = new APool<Map<string, Entity>>();

        @NoSerialize()
        private _instanceId = Long.fromNumber(0);
        public get instanceId(): Long {
            return this._instanceId;
        }
        public set instanceId(value: Long) {
            this._instanceId = value;
        }
        @NoSerialize()
        private status: number = EntityStatus.None;

        public get isFromPool(): boolean {
            return (this.status & EntityStatus.IsFromPool) == EntityStatus.IsFromPool;
        }
        public set isFromPool(value: boolean) {
            if (value) {
                this.status |= EntityStatus.IsFromPool;
            }
            else {
                this.status &= ~EntityStatus.IsFromPool;
            }

            if (this.instanceId.equals(0)) {
                this.instanceId = IdGenerater.GenerateInstanceId();
            }

            this.isRegister = value;
        }
        public get isRegister(): boolean {
            return (this.status & EntityStatus.IsRegister) == EntityStatus.IsRegister;
        }
        public set isRegister(value: boolean) {
            if (this.isRegister == value) {
                return;
            }
            if (value) {
                this.status |= EntityStatus.IsRegister;
            }
            else {
                this.status &= ~EntityStatus.IsRegister;
            }
            EventSystem.Instance.registSystem(this, value);
            if (this._children) {
                for (let kv of this._children) {
                    kv[1].isRegister = value;
                }
            }
            if (this._components) {
                for (let kv of this._components) {
                    kv[1].isRegister = value;
                }
            }
        }
        private get isComponent(): boolean {
            return (this.status & EntityStatus.IsComponent) == EntityStatus.IsComponent;
        }
        private set isComponent(value: boolean) {
            if (value) {
                this.status |= EntityStatus.IsComponent;
            }
            else {
                this.status &= ~EntityStatus.IsComponent;
            }
        }
        public get isCreate(): boolean {
            return (this.status & EntityStatus.IsCreate) == EntityStatus.IsCreate;
        }
        public set isCreate(value: boolean) {
            if (value) {
                this.status |= EntityStatus.IsCreate;
            }
            else {
                this.status &= ~EntityStatus.IsCreate;
            }
        }
        public get isDisposed(): boolean {
            return this.instanceId.equals(0);
        }
        @NoSerialize()
        protected _parent: Entity;
        public get parent(): Entity {
            return this._parent;

        }
        public set parent(value: Entity) {
            if (value == null) {
                LogUtil.error(`cant set parent null: ${this.constructor.name}`);
                return;
            }

            if (this._parent != null) // 之前有parent
            {
                // parent相同，不设置
                if (this._parent.instanceId == value.instanceId) {
                    LogUtil.warn(`重复设置了Parent: ${this.getType().name} parent: ${this.getType().name}`);
                    return;
                }

                this._parent.removeChild(this);
                this._parent = value;
                this._parent.addChild(this);

                this.domain = this._parent._domain;
            }
            else {
                this._parent = value;
                this._parent.addChild(this);

                this.isComponent = false;

                this.afterSetParent();
            }
        }
        private set componentParent(value: Entity) {
            if (this._parent != null) {
                LogUtil.error(`Component parent is null: ${this.constructor.name}`);
            }

            this._parent = value;

            this.isComponent = true;

            this.afterSetParent();
        }
        private afterSetParent(): void {
            this.domain = this.parent._domain;
        }
        public getParent<T extends Entity>(): T {
            return this.parent as T;
        }
        @Serialize()
        private _id: Long = Long.fromNumber(0);
        public get id(): Long {
            return this._id;
        }
        public set id(value: Long) {
            this._id = value;
        }
        @NoSerialize()
        protected _domain: Entity;
        public get domain(): Entity {
            return this._domain;
        }
        public set domain(value: Entity) {
            if (value == null) {
                return;
            }
            let preDomain = this._domain;
            this._domain = value;

            // if (!(domain is Scene))
            // {
            //     throw new Exception($"domain is not scene: {GetType().Name}");
            // }

            if (preDomain == null) {
                this.instanceId = IdGenerater.GenerateInstanceId();

                if (!this.isCreate) {
                    if (this.componentsDB != null) {
                        for (const component of this.componentsDB) {
                            component.isComponent = true;
                            this.components.set(component.getType(), component);
                            component._parent = this;
                        }
                    }
                    if (this.childrenDB != null) {
                        for (let child of this.childrenDB) {
                            child.isComponent = false;
                            this.children.set(child.id.toString(), child);
                            child._parent = this;
                        }
                    }
                }
            }

            // 是否注册跟parent一致
            if (this._parent != null) {
                this.isRegister = this.parent.isRegister;
            }

            // 递归设置孩子的Domain
            if (this.children != null) {
                for (let kv of this.children) {
                    kv[1].domain = this._domain;
                }
            }

            if (this.components != null) {
                for (let kv of this.components) {
                    kv[1].domain = this._domain;
                }
            }

            if (preDomain == null && !this.isCreate) {
                EventSystem.Instance.deserialize(this);
            }
        }
        @Serialize()
        private childrenDB: Set<Entity>;
        @NoSerialize()
        private _children: Map<string, Entity>;

        public get children(): Map<string, Entity> {
            if (this._children == null) {
                this._children = Entity.childrenPool.fetch(Map);
            }

            return this._children;
        }
        private addChild(entity: Entity): void {
            this.children.set(entity.id.toString(), entity);
            this.addChildDB(entity);
        }

        private removeChild(entity: Entity): void {
            if (this._children == null) {
                return;
            }

            this._children.delete(entity.id.toString());

            if (this._children.size == 0) {
                Entity.childrenPool.recycle(this._children);
                this._children = null;
            }

            this.removeChildDB(entity);
        }
        private addChildDB(entity: Entity) {
            if (!HasClassMap(entity.getType())) {
                return;
            }

            if (this.childrenDB == null) {
                this.childrenDB = Entity.hashSetPool.fetch(Set);
            }

            this.childrenDB.add(entity);
        }
        private removeChildDB(entity: Entity) {
            if (!HasClassMap(entity.getType())) {
                return;
            }

            if (this.childrenDB == null) {
                return;
            }

            this.childrenDB.delete(entity);

            if (this.childrenDB.size == 0) {
                if (this.isFromPool) {
                    Entity.hashSetPool.recycle(this.childrenDB);
                    this.childrenDB = null;
                }
            }
        }
        @Serialize()
        private componentsDB: Set<Entity>;
        @NoSerialize()
        private _components: Map<any, Entity>;
        public get components(): Map<any, Entity> {
            if (this._components == null) {
                this._components = Entity.dictPool.fetch(Map);
            }
            return this._components;
        }
        constructor() {
            super();
        }
        public dispose(): void {
            if (this.isDisposed) {
                return;
            }
            this.isRegister = false;
            this.instanceId = Long.fromNumber(0);

            // 清理Component
            if (this._components != null) {
                let deletes = this._components;
                this._components = null;
                for (let kv of deletes) {
                    kv[1].dispose();
                }

                deletes.clear();
                Entity.dictPool.recycle(deletes);

                if (this.isComponent) {
                    this._parent.removeComponentWithInstance(this);
                }
                else {
                    this._parent.removeChild(this);
                }

                this._parent = null;

                if (this.isFromPool) {
                    ObjectPool.Instance.recycle(this);
                }
                // else {
                //     if (this.node != null) {
                //         this.node.destroy();
                //     }
                // }

                this.status = EntityStatus.None;
            }

            this._domain = null;
            // 清理Children
            if (this._children != null) {
                let deletes = this.children;
                this._children = null;

                for (let child of deletes.values()) {
                    child.dispose();
                }

                deletes.clear();
                Entity.childrenPool.recycle(deletes);

            }
            // 触发Destroy事件
            EventSystem.Instance.destroy(this);
        }
        private addToComponentsDB(component: Entity) {
            if (this.componentsDB == null) {
                this.componentsDB = Entity.hashSetPool.fetch(Set);
            }

            this.componentsDB.add(component);
        }

        private removeFromComponentsDB(component: Entity) {
            if (this.componentsDB == null) {
                return;
            }

            this.componentsDB.delete(component);
            if (this.componentsDB.size == 0 && this.isFromPool) {
                Entity.hashSetPool.recycle(this.componentsDB);
                this.componentsDB = null;
            }
        }
        private addToComponent(type: { new() }, component: Entity): void {
            if (this._components == null) {
                this._components = Entity.dictPool.fetch(Map);
            }

            this._components.set(type.prototype, component);
            if (HasClassMap(type.prototype)) {
                this.addToComponentsDB(component);
            }
        }
        private removeFromComponent(type: { new() }, component: Entity): void {
            if (this._components == null) {
                return;
            }

            this._components.delete(type.prototype);

            if (this._components.size == 0 && this.isFromPool) {
                Entity.dictPool.recycle(this._components);
                this._components = null;
            }
            this.removeFromComponentsDB(component);
        }
        public addComponentWithInstance(component: Entity): Entity {
            component.componentParent = this;

            let type = component.getType();

            this.addToComponent(type, component);

            return component;
        }
        public addComponent<K extends Entity>(type: { new(): K }): K;
        public addComponent<K extends Entity, P1>(type: { new(): K }, p1: P1): K;
        public addComponent<K extends Entity, P1, P2>(type: { new(): K }, p1: P1, p2: P2): K;
        public addComponent<K extends Entity, P1, P2, P3>(type: { new(): K }, p1: P1, p2: P2, p3: P3): K;
        public addComponent<K extends Entity, P1, P2, P3>(type: { new(): K }, p1?: P1, p2?: P2, p3?: P3): K {

            let component: K;
            if (p1 == undefined && p2 == undefined && p3 == undefined) {
                component = this.createWithParent(type);
            }
            else if (p1 != undefined && p2 == undefined && p3 == undefined) {
                component = this.createWithParent(type, p1);
            }
            else if (p1 != undefined && p2 != undefined && p3 == undefined) {
                component = this.createWithParent(type, p1, p2);
            }
            else if (p1 != undefined && p2 != undefined && p3 != undefined) {
                component = this.createWithParent(type, p1, p2, p3);
            }
            this.addToComponent(type, component);

            return component;
        }
        public addComponentNoPool<K extends Entity>(type: { new(): K }): K;
        public addComponentNoPool<K extends Entity, P1>(type: { new(): K }, p1: P1): K;
        public addComponentNoPool<K extends Entity, P1, P2>(type: { new(): K }, p1: P1, p2: P2): K;
        public addComponentNoPool<K extends Entity, P1, P2, P3>(type: { new(): K }, p1: P1, p2: P2, p3: P3): K;
        public addComponentNoPool<K extends Entity, P1, P2, P3>(type: { new(): K }, p1?: P1, p2?: P2, p3?: P3): K {

            let component: K;
            if (p1 == undefined && p2 == undefined && p3 == undefined) {
                component = this.createWithParent(type);
            }
            else if (p1 != undefined && p2 == undefined && p3 == undefined) {
                component = this.createWithParent(type, p1);
            }
            else if (p1 != undefined && p2 != undefined && p3 == undefined) {
                component = this.createWithParent(type, p1, p2);
            }
            else if (p1 != undefined && p2 != undefined && p3 != undefined) {
                component = this.createWithParent(type, p1, p2, p3);
            }
            this.addToComponent(type, component);

            return component;
        }
        public removeComponent<K extends Entity>(type: { new(): K }): K {
            if (this.isDisposed) {
                return;
            }

            if (this._components == null) {
                return;
            }

            let c = this.getComponentWithType(type);
            if (c == null) {
                return;
            }

            this.removeFromComponent(type, c);
            c.dispose();
        }
        public removeComponentWithInstance(component: Entity): void {
            if (this.isDisposed) {
                return;
            }

            if (this._components == null) {
                return;
            }

            let type = component.getType();
            let c = this.getComponentWithType(type);
            if (c == null) {
                return;
            }
            if (c.instanceId != component.instanceId) {
                return;
            }

            this.removeFromComponent(type, c);
            c.dispose();
        }
        public removeComponentWithType(type: { new() }): void {
            if (this.isDisposed) {
                return;
            }

            let c = this.getComponentWithType(type);
            if (c == null) {
                return;
            }

            this.removeFromComponent(type, c);
            c.dispose();
        }
        public getComponent<K extends Entity>(type: { new(): K }): K {
            if (this._components == null) {
                return null;
            }
            let component = this._components.get(type.prototype);
            return component as K;
        }
        public getComponentWithType(type: { new() }): Entity {
            if (this._components == null) {
                return null;
            }
            let component = this._components.get(type.prototype);

            return component;
        }
        public static create<T extends Entity>(type: { new(): T }, isFromPool: boolean): Entity {
            let component: Entity;
            if (isFromPool) {
                component = ObjectPool.Instance.fetch(type);
            }
            else {
                component = new type();
            }
            component.isFromPool = isFromPool;
            component.isCreate = true;
            component.id = Long.fromNumber(0);
            return component;
        }
        private createWithParent<T extends Entity>(type: { new(): T }, isFromPool?: boolean): T;
        private createWithParent<T extends Entity, A>(type: { new(): T }, a: A, isFromPool?: boolean): T;
        private createWithParent<T extends Entity, A, B>(type: { new(): T }, a: A, b: B, isFromPool?: boolean): T;
        private createWithParent<T extends Entity, A, B, C>(type: { new(): T }, a: A, b: B, c: C, isFromPool?: boolean): T;
        private createWithParent<T extends Entity, A, B, C>(type: { new(): T }, a?: A, b?: B, c?: C, isFromPool = true): T {
            let component = Entity.create(type, isFromPool);

            component.id = this.id;
            component.componentParent = this;
            EventSystem.Instance.awake(component, a, b, c);
            return component as T;
        }
    }
}